#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <string>
#include <fcntl.h>

#include <sys/wait.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <signal.h>
#include <hiredis/hiredis.h>
#include <sys/time.h>
#include <sstream>
#include <fstream>
#include <dirent.h>
#include <sys/stat.h>

#include "redis.h" 

using namespace std;

#define IP  "172.27.0.15"
#define PORT 4040
#define MAX  1024

int64_t GetLocalTime()      //得到当前时间
{    
    struct timeval tv;    
    gettimeofday(&tv,NULL);    //头文件在sys/time.h中
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;    
}

void echo_erro(int sock,int status_code)
{
    switch(status_code)
    {
      case 400:
      {
          char* erro = "400";
          const char* head = "HTTP/1.0 400 Bad Request!";
          echo_html(sock, erro, head);

      }
      break;
      case 403:
      {
          char* erro = "403";
          const char* head = "HTTP/1.0 403 Forbidden!";
          echo_html(sock, erro, head);

      }
      break;
      case 404:
      {
          char* erro = "404";
          const char* head = "HTTP/1.0 404 Not Found!";
          echo_html(sock, erro, head);

      }
      break;
      case 500:
      {
          char* erro = "500";
          const char* head = "HTTP/1.0 400 Internal Server Error!";
          echo_html(sock, erro, head);

      }
      break;
      case 503:
      {
          char* erro = "503";
          const char* head = "HTTP/1.0 503 Server Unavailable!";
          echo_html(sock, erro, head);
      }
      break;
      default:
        break;
    }
}

void echo_html(int sock, char* status_code, const char* response)
{
    char erro[MAX/16];
    char path[MAX];
    //sprintf(erro,"/%s",code);
    //printf("erro:%s\n",erro);
    strcpy(erro+4,".html");
    sprintf(path,"wwwroot%s",erro);//将erro内容追加到wwwroot下并存在path中

    int fd = open(path, O_RDONLY);//响应的html文件
    //printf("htmlfd:%d \n",fd);

    char header_msg[MAX];//响应行
    strcpy(header_msg,response);

    //printf("header_msg:%s\n",header_msg);

    if(send(sock,header_msg, strlen(header_msg), 0) < 0)//发送报头信息
    {
        perror("send");
    }
    struct stat st;
    if(stat(path,&st)< 0)//判断是否是路径
    {
        perror("stat");
    }
    int Content_length = st.st_size;//响应报头
    char len[MAX];
    sprintf(len, "CONTENT-LENGTH:%d",Content_length);
    //printf("len:%s\n",len);

    if(send(sock,len, strlen(len), 0) < 0)
    {//发送响应报头
        perror("send");
    }
    const char* msg ="\r\n";
    if(send(sock,msg, strlen(msg), 0)< 0)//空行
    {
        perror("send");
    }
    //printf("send success\n");
    if(sendfile(sock,fd, NULL, st.st_size) < 0)//a发送正文
    {
        perror("sendfile");
    }
    //printf("ok\n");
    close(fd);
}

int StartUp()
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        exit(1);
    }
    int opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//强制进程立即使用处于TIME_WAIT状态的连接占用端口
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = inet_addr(IP);
    local.sin_port = htons(PORT);
    if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)//绑定端口号
    {
        perror("bind");
        exit(2);
    }
    if(listen(sock, 5) < 0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}

void clean_header(int sock)
{
  //清理头部,一直读完读到换行
  char buf[MAX];
  do
  {
      GetLine(sock,buf,MAX);
  }while(strcmp(buf,"\n") != 0);
}

string get_time()//获取当前日期
{
    time_t timep;
    time (&timep);
    char tmp[64];
    strftime(tmp, sizeof(tmp), "%Y-%m-%d",localtime(&timep) );
    return tmp;
}

int string_replace(string& str,const char* path)
{
    string mode="img src=\"xxxxx\" alt=\"false\"";
    string sub="img src=\"xxxxx\" alt=\"true\"";
    size_t pos=-1;
    if((pos=str.find(mode))!=string::npos)
    {
        size_t sub_pos=sub.find("xxxxx");
        sub.replace(sub_pos,5,path);//将照片目录添加到标签语言中
        str.replace(pos, mode.size(), sub);    //将mode替换为sub字符串
        return 0;
    }
    return -1;
}
int change_html_to_send(string& str,string& path)
{
    struct dirent *direntp;
    DIR *dirp = opendir(path.c_str());

    struct stat s_buf;
    string image_path="img/fun_images/";
    if (dirp != NULL) 
    {
        while ((direntp = readdir(dirp)) != NULL)
        {
            stat(direntp->d_name,&s_buf);、、
            if((strcmp(direntp->d_name,"..")!=0) && (strcmp(direntp->d_name,".")!=0))
            {
                if(string_replace(str,(image_path+direntp->d_name).c_str())!=0)
                {
                    break;
                }
            }
        }
    }
    closedir(dirp);
    return 200;
}

int send_html(int sock,char* path)
{
    string image_path="source/img/fun_images/";//动态加载的图片所在的路径
    Redis *r = new Redis();  
    if(!r->connect("127.0.0.1", 6179))  
    {  
        printf("connect error!\n");  
        return 404;  
    } 
    string today=get_time();
    today+=".html";
    string html_data=r->get(today);
    string road="/www/wwwroot/ftpuser/http/source/";
    road+=today;
    if(html_data.empty())//redis没有缓存
    {
        html_data=read_file_to_string(path);
        change_html_to_send(html_data,image_path);
        write_string_to_file((road).c_str(),html_data);//将string包含的字节流写成文件
        r->set(today,html_data);
    }
    int fd=open((road).c_str(),O_RDONLY);
    struct stat file;
    stat((road).c_str(),&file);

    if(sendfile(sock,fd,NULL,file.st_size) < 0)
    {
        perror("send...!");
    }
    delete r;
    return 200;
}

int find_point(char* str)
{
    char* cur=str;
    while(*str!='.')
    {
        ++str;
    }
    return str-cur;
}


static int echo_www(int sock,char* path, int size)//返回页面
{
    printf("path:%s\n",path);
    int fd = open(path,O_RDONLY);
    char header_msg[MAX];//响应行
    const char* response = "HTTP/1.0 200 OK!\r\n";
    strcpy(header_msg,response);

    if(send(sock,header_msg, strlen(header_msg)+1, 0) < 0)
    {
        perror("send");
    }

    int Content_length = size;//响应报头
    char len[MAX];
    char tail[5] = {0};
    char type[MAX] = {0};
    int index = find_point(path);
    strcpy(tail,path+index+1);
    if(strcmp(tail,"jpg") == 0)
    {
        char str[30]={0};
        strcpy(str,"image/jpeg\r\n");
        sprintf(type, "Content-Type:%s",str);
        printf("type:%s\n",type);
        if(send(sock,type, strlen(type)+1, 0) < 0)//发送响应报头
        {
            perror("send");
        }
    }
    else if(strcmp(tail,"css") == 0)
    {
        char str[30]={0};
        strcpy(str,"text/css\r\n");
        sprintf(type, "CONTENT-TYPE:%s",str);
        if(send(sock,type, strlen(type)+1, 0) < 0)//发送响应报头
        {
            perror("send");
        }
    }
    else if(strcmp(tail,"js") == 0)
    {
        char str[30]={0};
        strcpy(str,"application/x-javascript\r\n");
        sprintf(type, "CONTENT-TYPE:%s",str);
        if(send(sock,type, strlen(type)+1, 0) < 0)//发送响应报头
        {
          perror("send");
        }

    }
    else if(strcmp(tail,"gif") == 0)
    {
        char str[30]={0};
        strcpy(str,"image/gif\r\n");
        sprintf(type, "CONTENT-TYPE:%s",str);
        if(send(sock,type, strlen(type)+1, 0) < 0)//发送响应报头
        {
          perror("send");
        }

    }
    else if(strcmp(tail,"png") == 0)
    {
        char str[30]={0};
        strcpy(str,"image/png\r\n");
        sprintf(type, "CONTENT-TYPE:%s",str);
        if(send(sock,type, strlen(type)+1, 0) < 0)//发送响应报头
        {
          perror("send");
        }

    }
    else if(strcmp(tail,"html") == 0)
    {
        char str[30]={0};
        strcpy(str,"text/html\r\n");
        sprintf(type, "CONTENT-TYPE:%s",str);
        if(send(sock,type, strlen(type)+1, 0) < 0)//发送响应报头
        {
            perror("send");
        }

    }
    else if(strcasecmp(tail,"mp4") == 0)
    {
        char str[30]={0};
        strcpy(str,"video/mpeg4\r\n");
        sprintf(type, "CONTENT-TYPE:%s",str);
        if(send(sock,type, strlen(type), 0) < 0)//发送响应报头
        {
          perror("send");
        }
    }
    else if(strcmp(tail,"ico") == 0)
    {
      char str[30]={0};
      strcpy(str,"image/x-icon\r\n");
      sprintf(type, "CONTENT-TYPE:%s",str);
      printf("type:%s\n",type);
      if(send(sock,type, strlen(type), 0) < 0)//发送响应报头
      {
         perror("send");
      }

    }
    printf("length:%d\n",Content_length);
    sprintf(len, "CONTENT-LENGTH:%d\r\n",Content_length);
    printf("contnet-len:%s\n",len);
    if(send(sock,len, strlen(len), 0) < 0)//发送响应报头
    {
        perror("send");
    }
    const char* msg ="\r\n";
    if(send(sock,msg, strlen(msg), 0)< 0)//空行
    {
        perror("send");
    }
    
    if(strcmp(path,"source/home.html")==0)
    {
        return send_html(sock,path);//发送正文，即主页html页面
    }
    return sendfile(sock,fd,NULL,size);
}

string read_file_to_string(const char* filename)//读取文件字节流到string中
{
  ifstream ifile(filename);
  //将文件读入到ostringstream对象buf中
  ostringstream buf;
  char ch;
  while(buf&&ifile.get(ch))
  {
     buf.put(ch); 
  }
  //返回与流对象buf关联的字符串
  return buf.str();
}


int write_string_to_file(const char* file, const string& str )//将string包含的字节流写成文件
{
    ofstream OsWrite(file);
    OsWrite<<str;
    OsWrite.close();
    return 0;
}

void update_price_html(const char* file)//更新price.html页面的内容
{
    string str=read_file_to_string(file); 
    Redis *r = new Redis();  
    if(!r->connect("127.0.0.1", 6179))  
    {  
      printf("connect error!\n");  
    }
    string time=get_time();
    string price = r->get(time);
    if(price=="finish")
    {
        return;
    }
    r->set(time,"finish");
    string mode="future: true }]";
    string sub="{ label: \'";
    sub+=time;
    sub+="\', value: ";
    sub+=price;
    sub+=", future: true }]";
    size_t pos=-1;
    if((pos=str.find(mode))!=string::npos)
    {
        str.replace(pos+mode.size()-1,1, ",");  //用逗号替换str中从pos+mode长度-1开始的1个字符
        str.replace(pos+mode.size(),0, sub);    //从逗号开始后的位置追加新的价格字符串
    }

    write_string_to_file(file,str);//将数据写会到文件中
    delete r;
}

int GetLine(int sock, char line[], int size)
{
    int i = 0;
    char c = '\0';
    while(c != '\n' && i < size)
    {
        ssize_t s = recv(sock, &c, 1, 0);
        if(s < 0)
        {
            perror("recv");
            return 400;//客户端请求的语法错误，服务器无法理解
        }
        else if(s>0)
        {
          if(c == '\r')//判断下一个字符是否是'\n'，如果是'\n'读上来,修改，不是，说明读了一行
          {
              recv(sock, &c, 1, MSG_PEEK);//窥探式读取
              if(c == '\n')//表示这是'\r\n'
              {
                  recv(sock,&c,1,0);//那么直接将'\n'读取上来,此时c直接被改为'\n'
              }
              else//将'\r'直接置为'\n'
              {
                  c = '\n';
              }
          }
          line[i] = c;
          ++i;
        }
        else 
        {
            perror("client closed...!");
            return -1;
        }
    }
    line[i] = '\0';
    return i; 
}

void hander(int sock, int epoll_fd)
{
    char line[MAX];//提取一行
    char method[MAX/16];//方法
    char url[MAX];//url资源
    char path[MAX];//路径
    char* query_string;//参数
    int  status_code = 200;//状态码
    int  cgi = 0;//cgi参数
    //printf("sock:%d\n",sock);
    int start=GetLocalTime();
    int ret = GetLine(sock, line, sizeof(line));//读取一行数据
    int end1=GetLocalTime();
    printf("Getline() : %ld\n",end1-start);
    if(ret  <= 0)
    {
        status_code = 400;
        if(status_code != 200)
        {
            echo_erro(sock,status_code);
        }
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
        close(sock);
        return;
    }
    size_t  i = 0;
    size_t  j = 0;
    //GET / HTTP/1.0
    while((i < sizeof(line)-1) && j < sizeof(method)-1 && !isspace(line[j]))//以空格为分割,读取方法
    {
        method[j] = line[i];
        j++;
        i++;
    }
    method[j] = '\0';
    //printf("method:%s ",method);
    j = 0;
    i+=1; 
    //读取url
    while((i < sizeof(url)-1) && !isspace(line[i]))
    {
        url[j] = line[i];
        ++j;
        ++i;
    }
    url[j] = '\0';
    //printf("url:%s ",url);
    //表示读取到方法
    if(strcasecmp(method, "GET") == 0)//不区分大小写比较
    {
        //此时是GET方法，判断有没有参数，如果有参数，是cgi模式
        //url里面有？表示有参数，提取参数和路径
        query_string = url;//获取参数
        while(*query_string != '\0')
        {

            if(*query_string == '?')//表示有参数
            {
                *query_string = '\0';
                cgi = 1;
                ++query_string;
                break;
            }
            ++query_string;
        }
        //printf("path:%s ,url: %s,query_string: %s \n",path,url, query_string);
    }
    else if(strcasecmp(method, "POST") == 0)
    {
        cgi = 1;//是POST方法,是cgi处理
    }
    else//出错
    {
        status_code = 400;
        clean_header(sock);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
        close(sock);
        return;
    }
    sprintf(path,"source%s",url);

    //printf("path:%s\n",path);
    if(path[strlen(path)-1] == '/')
    {

        Redis *r = new Redis();  //创建Redis对象，准备连接数据库
        if(!r->connect("127.0.0.1", 6179))  
        {  
            printf("connect error!\n");  
        }
        string today=get_time();
        string html_data=r->get(today);
        if(html_data.empty())//redis没有缓存
        {
            strcat(path,"home.html");
        }
        else 
        {
            strcat(path,(today+".html").c_str());
        }
        delete r;
    }
    printf("path:%s \n",path);
    struct stat buf;
    if(stat(path,&buf) < 0)
    {
        status_code = 404;
        clean_header(sock);
        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
        close(sock);
        return;
    }
    else
    {
        if(strcmp(url,"/price.html")==0)
        {
           update_price_html(path);//更新价格
        }
        else if(S_ISREG(buf.st_mode))//如果是可执行文件
        {
            if((buf.st_mode & S_IXGRP) || (buf.st_mode & S_IXUSR) || (buf.st_mode & S_IXOTH))
            {
                cgi = 1;
            }
            }
            else
            {
            }

    }
    int end2=GetLocalTime();
    printf("判断请求的资源类型 : %ld\n",end2-start);
    printf("cgi:%d\n",cgi);
    if(cgi == 0)//开始响应,此时表示是GET方法,且目录绝对存在
    {
        clean_header(sock);
        int start=GetLocalTime();
        status_code = echo_www(sock,path,buf.st_size);
        // printf("%d\n",status_code);
        int end=GetLocalTime();
        printf("echo_www() : %ld\n",end-start);
    }
    else
    {
        //以后需要处理的cgi程序
    }

end:
    if(status_code != 200)
    {
        echo_erro(sock,status_code);
    }
    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock, NULL);
    close(sock);
}

void setNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}
void handlerReadyEvents(int epoll_fd, struct epoll_event  revents[],int num,int listen_sock)
{
    int i = 0;
    for(; i < num; ++i)
    {
        int fd = revents[i].data.fd;
        uint32_t events = revents[i].events;
        if(listen_sock == fd && (events & EPOLLIN))//表示监听到了新的链接，事件为读事件
        {
            //1.将事件添加到红黑树中，此时还没有就绪事件
            struct epoll_event ev;
            int socket = accept(fd,NULL,NULL);//建立连接
            if(socket < 0)
            {
                perror("accept");
            }
            printf("get new client...\n");
            ev.data.fd = socket;
            ev.events = EPOLLIN;
            if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev)==-1)//将该事件插入到红黑树中，并为该节点注册一个回调函数，当该事件就绪后
            //，则调用回调函数将其插入到就绪队列中。
            {
                perror("epoll_ctl...!");
                return;
            }
      }
      else if(events & EPOLLIN)//关心的读事件就绪,这里表示已经有数据可以读了
      {
          //读取一行,来分析数据,是get还是cgi，还是返回页面
          hander(fd,epoll_fd); 
      }
    }
}
int main()
{
    int64_t start=GetLocalTime();//获取当前时间
    int listen_fd = StartUp();//获取监听套接字
    int64_t end=GetLocalTime();
    printf("Start() : %ld\n",end-start);
    start=GetLocalTime();
    int epoll_fd = epoll_create(65535);//创建65535大小的事件表,返回事件表文件描述符
    if(epoll_fd < 0)
    {
        perror("epoll_create");
        return 1;
    }
    end=GetLocalTime();
    printf("epoll_create() : %ld\n",end-start);

    struct epoll_event events;
    events.data.fd = listen_fd;//将所要监听的事件存储在events中
    events.events = EPOLLIN;//想要关心读事件

    start=GetLocalTime();
    int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &events);//给listen_fd文件描述符添加events事件中（插入红黑树）
    if(ret < 0)
    {
        perror("epoll_ctl");
        return 2;
    }
    end=GetLocalTime();
    printf("epoll_ctl() : %ld\n",end-start);

    struct epoll_event revents[2048];
    for(; ; )
    {
        int timeout = -1;
        int num = epoll_wait(epoll_fd,revents,sizeof(revents)/sizeof(revents[0]),timeout);//一直阻塞等待事件发生
        switch(num)
        {
          case -1:
            perror("epoll_wait");
            break;
          case 0:
            printf("timeout...\n");
            break;
          default:
            {
                start=GetLocalTime();
                handlerReadyEvents(epoll_fd, revents,num,listen_fd);
                end=GetLocalTime();
                printf("handlerReadyEvents() : %ld\n",end-start);
            }
        }
    }
    close(listen_fd);
    close(epoll_fd);
} 
